"""
Binance交易所适配器

支持现货和合约交易
"""

import time
import hmac
import hashlib
from datetime import datetime
from typing import Dict, List, Optional, Any
import requests
import pandas as pd

from .base_exchange import BaseExchange
from ..core import unified_logger
from ..core.exceptions import ExchangeAPIError, NetworkError, ValidationError


class BinanceExchange(BaseExchange):
    """Binance交易所适配器"""
    
    def __init__(self, config: dict):
        super().__init__(config)
        self.name = 'binance'
        
        # API配置
        self.api_key = config.get('api_key', '')
        self.secret_key = config.get('secret_key', '')
        self.testnet = config.get('testnet', True)
        
        # API端点
        if self.testnet:
            self.spot_base_url = 'https://testnet.binance.vision'
            self.futures_base_url = 'https://testnet.binancefuture.com'
        else:
            self.spot_base_url = 'https://api.binance.com'
            self.futures_base_url = 'https://fapi.binance.com'
        
        self.logger = unified_logger.get_logger(f"binance_exchange")
        
        # 费率配置
        self.spot_maker_fee = config.get('fees', {}).get('spot', {}).get('maker', 0.001)
        self.spot_taker_fee = config.get('fees', {}).get('spot', {}).get('taker', 0.001)
        self.futures_maker_fee = config.get('fees', {}).get('futures', {}).get('maker', 0.0002)
        self.futures_taker_fee = config.get('fees', {}).get('futures', {}).get('taker', 0.0004)
    
    def _generate_signature(self, query_string: str) -> str:
        """生成签名"""
        return hmac.new(
            self.secret_key.encode('utf-8'),
            query_string.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
    
    def _get_headers(self) -> Dict[str, str]:
        """获取请求头"""
        return {
            'X-MBX-APIKEY': self.api_key,
            'Content-Type': 'application/json'
        }
    
    def _make_request(self, method: str, endpoint: str, base_url: str,
                     params: Optional[Dict] = None, 
                     signed: bool = False) -> Dict:
        """发送API请求"""
        url = f"{base_url}{endpoint}"
        headers = self._get_headers()
        
        if params is None:
            params = {}
        
        # 添加时间戳
        if signed:
            params['timestamp'] = int(time.time() * 1000)
            
            # 生成查询字符串
            query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
            
            # 生成签名
            signature = self._generate_signature(query_string)
            params['signature'] = signature
        
        try:
            if method == 'GET':
                response = requests.get(url, headers=headers, params=params, timeout=10)
            elif method == 'POST':
                response = requests.post(url, headers=headers, params=params, timeout=10)
            elif method == 'DELETE':
                response = requests.delete(url, headers=headers, params=params, timeout=10)
            else:
                raise ValueError(f"不支持的HTTP方法: {method}")
            
            response.raise_for_status()
            result = response.json()
            
            # 检查API错误
            if 'code' in result and result['code'] != 200:
                error_msg = result.get('msg', '未知错误')
                raise ExchangeAPIError(f"Binance API错误: {error_msg}", 
                                     exchange='binance', 
                                     error_code=str(result['code']))
            
            return result
            
        except requests.exceptions.Timeout:
            self.logger.error(f"API请求超时: {url}")
            raise NetworkError(f"请求超时: {endpoint}", url=url)
        except requests.exceptions.ConnectionError:
            self.logger.error(f"网络连接错误: {url}")
            raise NetworkError(f"连接失败: {endpoint}", url=url)
        except requests.exceptions.HTTPError as e:
            self.logger.error(f"HTTP错误: {e}")
            if e.response.status_code == 429:
                raise ExchangeAPIError("API请求频率限制", exchange='binance', error_code='429')
            elif e.response.status_code >= 500:
                raise NetworkError(f"服务器错误: {e.response.status_code}", url=url)
            else:
                raise ExchangeAPIError(f"HTTP错误: {e}", exchange='binance')
        except requests.exceptions.RequestException as e:
            self.logger.error(f"API请求失败: {e}")
            raise NetworkError(f"请求失败: {e}", url=url)
        except ValueError as e:
            self.logger.error(f"JSON解析失败: {e}")
            raise ValidationError(f"响应格式错误: {e}")
        except Exception as e:
            self.logger.error(f"处理响应失败: {e}")
            raise ExchangeAPIError(f"未知错误: {e}", exchange='binance')
    
    def connect(self) -> bool:
        """连接到交易所"""
        try:
            # 测试现货连接
            self._make_request('GET', '/api/v3/account', self.spot_base_url, signed=True)
            self.connected = True
            self.last_heartbeat = datetime.now()
            self.logger.info("Binance连接成功")
            return True
        except Exception as e:
            self.logger.error(f"Binance连接失败: {e}")
            self.connected = False
            return False
    
    def disconnect(self):
        """断开连接"""
        self.connected = False
        self.logger.info("Binance连接已断开")
    
    def get_spot_balance(self, asset: str = None) -> Dict[str, Dict[str, float]]:
        """获取现货余额"""
        try:
            result = self._make_request('GET', '/api/v3/account', self.spot_base_url, signed=True)
            balances = {}
            
            for balance in result.get('balances', []):
                currency = balance.get('asset')
                if asset and currency != asset:
                    continue
                
                free_balance = float(balance.get('free', 0))
                locked_balance = float(balance.get('locked', 0))
                
                if free_balance > 0 or locked_balance > 0:
                    balances[currency] = {
                        'free': free_balance,
                        'locked': locked_balance,
                        'total': free_balance + locked_balance
                    }
            
            return balances if not asset else balances.get(asset, {})
            
        except Exception as e:
            self.logger.error(f"获取现货余额失败: {e}")
            return {}
    
    def place_spot_order(self, symbol: str, side: str, order_type: str,
                        quantity: float, price: float = None, **kwargs) -> str:
        """现货下单"""
        try:
            # 转换交易对格式
            if '-' in symbol:
                symbol = symbol.replace('-', '')
            
            order_params = {
                'symbol': symbol,
                'side': side.upper(),
                'type': order_type.upper(),
                'quantity': quantity
            }
            
            if order_type.upper() == 'LIMIT':
                if price is None:
                    raise ValueError("限价单必须指定价格")
                order_params['price'] = price
                order_params['timeInForce'] = kwargs.get('timeInForce', 'GTC')
            
            result = self._make_request('POST', '/api/v3/order', 
                                      self.spot_base_url, order_params, signed=True)
            
            order_id = str(result.get('orderId'))
            self.logger.info(f"现货订单已提交: {order_id}")
            return order_id
                
        except Exception as e:
            self.logger.error(f"现货下单失败: {e}")
            raise
    
    def get_futures_balance(self) -> Dict[str, Dict[str, float]]:
        """获取合约余额"""
        try:
            result = self._make_request('GET', '/fapi/v2/balance', 
                                      self.futures_base_url, signed=True)
            balances = {}
            
            for balance in result:
                currency = balance.get('asset')
                balances[currency] = {
                    'free': float(balance.get('availableBalance', 0)),
                    'locked': float(balance.get('balance', 0)) - float(balance.get('availableBalance', 0)),
                    'total': float(balance.get('balance', 0)),
                    'unrealized_pnl': float(balance.get('crossUnPnl', 0))
                }
            
            return balances
            
        except Exception as e:
            self.logger.error(f"获取合约余额失败: {e}")
            return {}
    
    def place_futures_order(self, symbol: str, side: str, order_type: str,
                           quantity: float, price: float = None, **kwargs) -> str:
        """合约下单"""
        try:
            # 转换交易对格式
            if '-' in symbol:
                symbol = symbol.replace('-', '')
            
            order_params = {
                'symbol': symbol,
                'side': side.upper(),
                'type': order_type.upper(),
                'quantity': quantity
            }
            
            if order_type.upper() == 'LIMIT':
                if price is None:
                    raise ValueError("限价单必须指定价格")
                order_params['price'] = price
                order_params['timeInForce'] = kwargs.get('timeInForce', 'GTC')
            
            # 持仓方向
            position_side = kwargs.get('position_side', 'BOTH')
            if position_side != 'BOTH':
                order_params['positionSide'] = position_side.upper()
            
            result = self._make_request('POST', '/fapi/v1/order', 
                                      self.futures_base_url, order_params, signed=True)
            
            order_id = str(result.get('orderId'))
            self.logger.info(f"合约订单已提交: {order_id}")
            return order_id
                
        except Exception as e:
            self.logger.error(f"合约下单失败: {e}")
            raise
    
    def cancel_order(self, symbol: str, order_id: str, **kwargs) -> bool:
        """撤销订单"""
        try:
            # 转换交易对格式
            if '-' in symbol:
                symbol = symbol.replace('-', '')
            
            trade_type = kwargs.get('trade_type', 'spot')
            
            cancel_params = {
                'symbol': symbol,
                'orderId': order_id
            }
            
            if trade_type == 'futures':
                endpoint = '/fapi/v1/order'
                base_url = self.futures_base_url
            else:
                endpoint = '/api/v3/order'
                base_url = self.spot_base_url
            
            result = self._make_request('DELETE', endpoint, base_url, 
                                      cancel_params, signed=True)
            
            self.logger.info(f"订单已撤销: {order_id}")
            return True
                
        except Exception as e:
            self.logger.error(f"撤销订单失败: {e}")
            return False
    
    def get_order_status(self, symbol: str, order_id: str, **kwargs) -> Dict:
        """获取订单状态"""
        try:
            # 转换交易对格式
            if '-' in symbol:
                symbol = symbol.replace('-', '')
            
            trade_type = kwargs.get('trade_type', 'spot')
            
            params = {
                'symbol': symbol,
                'orderId': order_id
            }
            
            if trade_type == 'futures':
                endpoint = '/fapi/v1/order'
                base_url = self.futures_base_url
            else:
                endpoint = '/api/v3/order'
                base_url = self.spot_base_url
            
            result = self._make_request('GET', endpoint, base_url, params, signed=True)
            
            return {
                'order_id': str(result.get('orderId')),
                'symbol': result.get('symbol'),
                'side': result.get('side'),
                'type': result.get('type'),
                'quantity': float(result.get('origQty', 0)),
                'price': float(result.get('price', 0)),
                'filled_quantity': float(result.get('executedQty', 0)),
                'status': result.get('status'),
                'timestamp': int(result.get('time', 0))
            }
                
        except Exception as e:
            self.logger.error(f"获取订单状态失败: {e}")
            return {}
    
    def get_klines(self, symbol: str, interval: str, limit: int = 100,
                   start_time: Optional[int] = None, end_time: Optional[int] = None,
                   trade_type: str = 'spot') -> pd.DataFrame:
        """获取K线数据"""
        try:
            # 转换交易对格式
            if '-' in symbol:
                symbol = symbol.replace('-', '')
            
            # 转换时间间隔
            interval_map = {
                '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m',
                '1h': '1h', '2h': '2h', '4h': '4h', '6h': '6h', '8h': '8h', '12h': '12h',
                '1d': '1d', '3d': '3d', '1w': '1w', '1M': '1M'
            }
            binance_interval = interval_map.get(interval, '1m')
            
            params = {
                'symbol': symbol,
                'interval': binance_interval,
                'limit': min(limit, 1000)  # Binance最大1000
            }
            
            if start_time:
                params['startTime'] = start_time
            if end_time:
                params['endTime'] = end_time
            
            if trade_type == 'futures':
                endpoint = '/fapi/v1/klines'
                base_url = self.futures_base_url
            else:
                endpoint = '/api/v3/klines'
                base_url = self.spot_base_url
            
            result = self._make_request('GET', endpoint, base_url, params)
            
            if not result:
                return pd.DataFrame()
            
            # 转换数据格式
            columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume',
                      'close_time', 'quote_volume', 'trades', 'taker_buy_volume',
                      'taker_buy_quote_volume', 'ignore']
            
            df = pd.DataFrame(result, columns=columns)
            
            # 数据类型转换
            df['timestamp'] = pd.to_numeric(df['timestamp'])
            df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
            
            for col in ['open', 'high', 'low', 'close', 'volume']:
                df[col] = pd.to_numeric(df[col])
            
            # 按时间排序
            df = df.sort_values('timestamp').reset_index(drop=True)
            
            return df[['datetime', 'timestamp', 'open', 'high', 'low', 'close', 'volume']]
            
        except Exception as e:
            self.logger.error(f"获取K线数据失败: {e}")
            return pd.DataFrame()
    
    def get_ticker(self, symbol: str, trade_type: str = 'spot') -> Dict:
        """获取行情数据"""
        try:
            # 转换交易对格式
            if '-' in symbol:
                symbol = symbol.replace('-', '')
            
            params = {'symbol': symbol}
            
            if trade_type == 'futures':
                endpoint = '/fapi/v1/ticker/24hr'
                base_url = self.futures_base_url
            else:
                endpoint = '/api/v3/ticker/24hr'
                base_url = self.spot_base_url
            
            result = self._make_request('GET', endpoint, base_url, params)
            
            return {
                'symbol': result.get('symbol'),
                'price': float(result.get('lastPrice', 0)),
                'bid': float(result.get('bidPrice', 0)),
                'ask': float(result.get('askPrice', 0)),
                'volume': float(result.get('volume', 0)),
                'change': float(result.get('priceChange', 0)),
                'change_percent': float(result.get('priceChangePercent', 0)),
                'timestamp': int(result.get('closeTime', 0))
            }
                
        except Exception as e:
            self.logger.error(f"获取行情数据失败: {e}")
            return {}
    
    def get_positions(self, symbol: str = None) -> List[Dict]:
        """获取持仓信息"""
        try:
            params = {}
            if symbol:
                if '-' in symbol:
                    symbol = symbol.replace('-', '')
                params['symbol'] = symbol
            
            result = self._make_request('GET', '/fapi/v2/positionRisk', 
                                      self.futures_base_url, params, signed=True)
            
            positions = []
            for pos_data in result:
                position_amt = float(pos_data.get('positionAmt', 0))
                if position_amt != 0:  # 只返回有持仓的
                    positions.append({
                        'symbol': pos_data.get('symbol'),
                        'side': 'long' if position_amt > 0 else 'short',
                        'size': abs(position_amt),
                        'entry_price': float(pos_data.get('entryPrice', 0)),
                        'mark_price': float(pos_data.get('markPrice', 0)),
                        'unrealized_pnl': float(pos_data.get('unRealizedProfit', 0)),
                        'margin': float(pos_data.get('isolatedMargin', 0)),
                        'timestamp': int(time.time() * 1000)
                    })
            
            return positions
            
        except Exception as e:
            self.logger.error(f"获取持仓信息失败: {e}")
            return []
    
    def get_trading_fee(self, symbol: str, trade_type: str = 'spot') -> Dict[str, float]:
        """获取交易费率"""
        if trade_type == 'spot':
            return {
                'maker': self.spot_maker_fee,
                'taker': self.spot_taker_fee
            }
        else:
            return {
                'maker': self.futures_maker_fee,
                'taker': self.futures_taker_fee
            }
    
    def validate_symbol(self, symbol: str, trade_type: str = 'spot') -> bool:
        """验证交易对"""
        try:
            ticker = self.get_ticker(symbol, trade_type)
            return bool(ticker)
        except:
            return False